#include "apricot.h"
#include "Icon.h"
#include "img_conv.h"
#include <Icon.inc>

#ifdef __cplusplus
extern "C" {
#endif


#undef  my
#define inherited CImage->
#define my  ((( PIcon) self)-> self)
#define var (( PIcon) self)

static void
produce_mask( Handle self)
{
   Byte * area8 = var-> data;
   Byte * dest = var-> mask;
   Byte * src;
   Byte color = 0;
   RGBColor rgbcolor;
   int i, bpp2;
   int line8Size = LINE_SIZE(var->w, 8), areaLineSize = var-> lineSize;
   int bpp = var-> type & imBPP;
   int w = var-> w, h = var-> h;

   if ( var-> w == 0 || var-> h == 0) return;

   if ( var-> autoMasking == amNone) return;

   if ( var-> autoMasking == amMaskColor) {
      rgbcolor. b = var-> maskColor & 0xFF;
      rgbcolor. g = (var-> maskColor >> 8)  & 0xFF;
      rgbcolor. r = (var-> maskColor >> 16) & 0xFF;
      if ( bpp <= 8)
         color = cm_nearest_color( rgbcolor, var-> palSize, var-> palette);
   } else if ( var-> autoMasking == amMaskIndex) {
      if ( bpp > 8) return;	 
      color = var-> maskIndex;
      bzero( &rgbcolor, sizeof(rgbcolor));
   }

   if ( bpp == imMono) {
      /* mono case simplifies our task */
      int  j = var-> maskSize;
      Byte * mask = var-> mask;
      memcpy ( var-> mask, var-> data, var-> dataSize);
      if ( color == 0) {
         while ( j--) mask[ j] = ~mask[ j];
      }
      var-> palette[color]. r = var-> palette[color]. g = var-> palette[color]. b = 0;
      if ( color > 0)
         var-> type &= ~imGrayScale;
      return;
   }

   /* convert to 8 bit */
   switch ( bpp)
   {
   case im16:
   case im256:
   case imRGB:
      bpp2 = bpp;
      break;
   default:
      bpp2  = im256;
      areaLineSize = line8Size;
      if (!( area8 = allocb( var-> h * line8Size))) return;
      ic_type_convert( self, area8, var-> palette, im256 | ( var-> type & imGrayScale), &var-> palSize, false);
      break;
   }

   if ( var-> autoMasking == amAuto) {  /* calculate transparent color */
      Byte corners [4];
      Byte counts  [4] = {1, 1, 1, 1};
      RGBColor rgbcorners[4];
      int j = areaLineSize, k;

      /* retrieving corner pixels */
      switch ( bpp2) {
      case im16:
         corners[ 0] = area8[ 0] >> 4;
         corners[ 1] = area8[( w - 1) >> 1];
         corners[ 1] = (( w - 1) & 1) ? corners[ 1] & 0x0f : corners[ 1] >> 4;
         corners[ 2] = area8[ j * ( h - 1)] >> 4;
         corners[ 3] = area8[ j * ( h - 1) + (( w - 1) >> 1)];
         corners[ 3] = (( w - 1) & 1) ? corners[ 3] & 0x0f : corners[ 3] >> 4;
         for ( j = 0; j < 4; j++) {
            rgbcorners[j].r = var-> palette[ corners[ j]]. r;
            rgbcorners[j].g = var-> palette[ corners[ j]]. g;
            rgbcorners[j].b = var-> palette[ corners[ j]]. b;
         }
         break;
      case im256:
         corners[ 0] = area8[ 0];
         corners[ 1] = area8[ w - 1];
         corners[ 2] = area8[ j * ( h - 1)];
         corners[ 3] = area8[ j * ( h - 1) + w - 1];
         for ( j = 0; j < 4; j++) {
            rgbcorners[j].r = var-> palette[ corners[ j]]. r;
            rgbcorners[j].g = var-> palette[ corners[ j]]. g;
            rgbcorners[j].b = var-> palette[ corners[ j]]. b;
         }
         break;
      case imRGB:
         rgbcorners[0] = *(PRGBColor)( area8);
         rgbcorners[1] = *(PRGBColor)( area8 + ( w - 1) * 3);
         rgbcorners[2] = *(PRGBColor)( area8 + j * ( h - 1));
         rgbcorners[3] = *(PRGBColor)( area8 + j * ( h - 1) + ( w - 1) * 3);
         for ( j = 0; j < 4; j++) corners[j] = j;
         #define rgbcmp(x,y) ((rgbcorners[x].r == rgbcorners[y].r) &&\
                              (rgbcorners[x].g == rgbcorners[y].g) &&\
                              (rgbcorners[x].b == rgbcorners[y].b))
         if ( rgbcmp(1,0)) corners[1] = 0;
         if ( rgbcmp(2,0)) corners[2] = 0;
         if ( rgbcmp(3,0)) corners[3] = 0;
         if ( rgbcmp(2,1)) corners[2] = corners[1];
         if ( rgbcmp(3,1)) corners[3] = corners[1];
         if ( rgbcmp(3,2)) corners[3] = corners[2];
         #undef rgbcmp
         break;
      }

      /* preliminary comparison to a transparent color candidate ( hack ) */
      for ( j = 0; j < 4; j++) {
         if (
             (( rgbcorners[j]. b) == 0) &&
             (( rgbcorners[j]. g) == 128) &&
             (( rgbcorners[j]. r) == 128)) {
            color = corners[ j];
            rgbcolor = rgbcorners[ j];
            goto colorFound;
         }
      }

      color = corners[ 3]; /* our wild (and possibly bad) guess */
      rgbcolor = rgbcorners[ 3];

      /* sorting */
      for ( j = 0; j < 3; j++)
         for (k = 0; k < 3; k++)
            if ( corners[ k] < corners[ k + 1]) {
               Byte l = corners[ k];
               corners[ k] = corners[ k + 1];
               corners[ k + 1] = l;
            }
      /* forming maximum's vector */
      i = 0;
      for (j = 0; j < 3; j++) if ( corners[ j + 1] == corners[ j]) counts[ i]++; else i++;
      for (j = 0; j < 3; j++)
         for (k = 0; k < 3; k++)
            if ( counts[ k] < counts[ k + 1]) {
               Byte l = counts[ k];
               counts[ k] = counts[ k + 1];
               counts[ k + 1] = l;
               l = corners[ k];
               corners[ k] = corners[ k + 1];
               corners[ k + 1] = l;
            }
      if (( counts[0] > 2) || (( counts[0] == 2) && ( counts[1] == 1))) {
        color = corners[ 0];
        rgbcolor = rgbcorners[ 0];
      } else {
         int colorsToCompare = ( counts[0] == 2) ? 2 : 4;

         /* compare to that yellowish hue... */
         for ( j = 0; j < colorsToCompare; j++)
            if (( rgbcorners[j]. b < 20) &&
                ( rgbcorners[j]. r > 100) &&
                ( rgbcorners[j]. r < 150) &&
                ( rgbcorners[j]. g > 100) &&
                ( rgbcorners[j]. g < 150))
            {
               color = corners[ j];
               rgbcolor = rgbcorners[ j];
               goto colorFound;
            }

         /* compare to MicroSoft Pink */
         for ( j = 0; j < colorsToCompare; j++)
            if (( rgbcorners[j]. g < 20) &&
                ( rgbcorners[j]. r > 200) &&
                ( rgbcorners[j]. b > 200))
            {
               color = corners[ j];
               rgbcolor = rgbcorners[ j];
               goto colorFound;
            }
      }
colorFound:;
   } 

   /* processing transparency */
   memset( var-> mask, 0, var-> maskSize);
   src  = area8;
   for ( i = 0; i < h; i++, dest += var-> maskLine, src += areaLineSize) {
      register int j;
      switch ( bpp2) {
      case im16:
         {
            int max = ( w >> 1) + ( w & 1);
            register int k = 0;
            for ( j = 0; j < max; j++) {
               if ( color == ( src[ j] >> 4))
                  dest[ k >> 3] |= 1 << (7 - ( k & 7));
               if ( color == ( src[ j] & 0x0f))
                  dest[ k >> 3] |= 1 << (6 - ( k & 7));
               k += 2;
            }
         }
         break;
      case imRGB:
         {
            register PRGBColor r = ( PRGBColor) src;
            for ( j = 0; j < w; j++) {
               if (( r-> r == rgbcolor.r) &&
                   ( r-> g == rgbcolor.g) &&
                   ( r-> b == rgbcolor.b))
                   dest[ j >> 3] |= 1 << (7 - ( j & 7));
               r++;
            }
         }
         break;
      default:
         for ( j = 0; j < w; j++)
            if ( src[ j] == color)
               dest[ j >> 3] |= 1 << (7 - ( j & 7));
      }
   }

   /* finalize */
   if ( var-> data != area8) free( area8);
   if ( var-> palSize > color && bpp <= im256) {
      var-> palette[ color]. r = var-> palette[ color]. b = var-> palette[ color]. g = 0;
      if ( color > 0)
         var-> type &= ~imGrayScale;
   }
}

void
Icon_init( Handle self, HV * profile)
{
   dPROFILE;
   inherited init( self, profile);
   my-> set_maskType( self, pget_i( maskType));
   my-> update_change(self); /* instantiate mask memory */

   my-> set_maskColor( self, pget_i( maskColor));
   my-> set_maskIndex( self, pget_i( maskIndex));
   my-> set_autoMasking( self, pget_i( autoMasking));
   my-> set_mask( self, pget_sv( mask));
   CORE_INIT_TRANSIENT(Icon);
}


SV *
Icon_mask( Handle self, Bool set, SV * svmask)
{
   STRLEN maskSize;
   void * mask;
   int am = var-> autoMasking;
   if ( var-> stage > csFrozen) return nilSV;
   if ( !set)
      return newSVpvn(( char *) var-> mask, var-> maskSize);
   mask = SvPV( svmask, maskSize);
   if ( is_opt( optInDraw) || maskSize <= 0) return nilSV;
   memcpy( var-> mask, mask, maskSize > var-> maskSize ? var-> maskSize : maskSize);
   var-> autoMasking = amNone;
   my-> update_change( self);
   var-> autoMasking = am;
   return nilSV;
}

int
Icon_autoMasking( Handle self, Bool set, int autoMasking)
{
   if ( !set)
      return var-> autoMasking;
   if ( var-> autoMasking == autoMasking) return 0;
   var-> autoMasking = autoMasking;
   if ( is_opt( optInDraw)) return 0;
   my-> update_change( self);
   return 0;
}   

/*

1-bit format is a true AND-mask: 0 is alpha=1, 1 is alpha=0
8-bit format has alpha value from 0 to 255. 

Note that inter-conversion between these negates the pixel values

*/
Byte*
Icon_convert_mask( Handle self, int type )
{
   int i;
   int srcLine = LINE_SIZE( var-> w, var-> maskType );
   int dstLine = LINE_SIZE( var-> w, type );
   Byte colorref[256], *src = var-> mask, *dst, *ret;
   RGBColor palette[2];

   if ( type == var-> maskType ) 
      croak("invalid usage of Icon::convert_mask");
   if ( !( ret = malloc( dstLine * var-> h))) {
      warn("Icon::convert_mask: cannot allocate %d bytes", dstLine * var-> h);
      return NULL;
   }

   switch (type) {
   case imbpp1:
      /* downgrade */
      memset( colorref,     1, 1   );
      memset( colorref + 1, 0, 255 );
      for ( i = 0, dst = ret; i < var->h; i++, src += srcLine, dst += dstLine)
          bc_byte_mono_cr( src, dst, var-> w, colorref);
      break;
   case imbpp8:
      /* upgrade */
      memset( &palette[0], 0xff, sizeof(RGBColor));
      memset( &palette[1], 0x00, sizeof(RGBColor));
      for ( i = 0, dst = ret; i < var->h; i++, src += srcLine, dst += dstLine)
          bc_mono_graybyte( src, dst, var-> w, palette);
      break;
   default:
      croak("invalid usage of Icon::convert_mask");
   }
   return ret;
}

int
Icon_maskType( Handle self, Bool set, int type)
{
   if ( !set)
      return var-> maskType;

   type &= ~imGrayScale;
   if ( var-> maskType == type) return 0;
   switch ( type ) {
   case imbpp1:
   case imbpp8:
      if ( var-> mask ) {
          Byte * new_mask = Icon_convert_mask(self, type);
	  free( var-> mask );
	  var-> mask = new_mask;
	  var-> maskLine = LINE_SIZE( var-> w, type );
	  var-> maskSize = var-> maskLine * var-> h;
      }
      break;
   default:
      croak("mask type must be either im::bpp1 or im::bpp8");
   }

   var-> maskType = type;
}

Color
Icon_maskColor( Handle self, Bool set, Color color)
{
   if ( !set)
      return var-> maskColor;
   if ( var-> maskColor == color) return 0;
   var-> maskColor = color;
   if ( is_opt( optInDraw)) return 0;
   if ( var-> autoMasking == amMaskColor) 
      my-> update_change( self);
   return clInvalid;
}   

int
Icon_maskIndex( Handle self, Bool set, int index)
{
   if ( !set)
      return var-> maskIndex;
   var-> maskIndex = index;
   if ( is_opt( optInDraw)) return 0;
   if ( var-> autoMasking == amMaskIndex) 
      my-> update_change( self);
   return -1;
}

void
Icon_update_change( Handle self)
{
   inherited update_change( self);

   if ( var-> maskType == 0) return; /* inside inherited init */

   if ( var-> autoMasking == amNone) {
      int maskLine = LINE_SIZE( var-> w, var-> maskType );
      int maskSize = maskLine * var-> h;
      if ( maskLine != var-> maskLine || maskSize != var-> maskSize) {
         free( var-> mask);
         var-> maskLine = maskLine;
         if (!( var-> mask = allocb( var-> maskSize = maskSize)) && maskSize > 0) {
            my-> make_empty( self);
            warn("Not enough memory: %d bytes", maskSize);
         } else
            memset( var-> mask, 0, maskSize);
      }
      return;
   }
   
   free( var-> mask);
   if ( var-> data)
   {
      int oldtype = var-> maskType;
      var-> maskType = imbpp1;
      var-> maskLine = LINE_SIZE( var-> w, var-> maskType );
      var-> maskSize = var-> maskLine * var-> h;
      if ( !( var-> mask = allocb( var-> maskSize)) && var-> maskSize > 0) {
          my-> make_empty( self);
          warn("Not enough memory: %d bytes", var-> maskSize);
          return;
      }
      produce_mask( self);
      if ( oldtype != imbpp1 )
         my-> set_maskType( self, oldtype);
   }
   else
      var-> mask = nil;
}

void
Icon_stretch( Handle self, int width, int height)
{
   Byte * newMask = nil;
   int lineSize, oldW = var-> w, oldH = var-> h, am = var-> autoMasking;
   if ( var->stage > csFrozen) return;
   if ( width  >  65535) width  =  65535;
   if ( height >  65535) height =  65535;
   if ( width  < -65535) width  = -65535;
   if ( height < -65535) height = -65535;
   if (( width == var->w) && ( height == var->h)) return;
   if ( width == 0 || height == 0)
   {
      my->create_empty( self, 0, 0, var->type);
      return;
   }
   
   lineSize = LINE_SIZE( abs( width), var-> maskType );
   newMask  = allocb( lineSize * abs( height));
   if ( newMask == nil && lineSize > 0) {
      my-> make_empty( self);
      croak("Icon::stretch: cannot allocate %d bytes", lineSize * abs( height));
   }
   var-> autoMasking = amNone;
   if ( var-> mask) {
      Bool hScaling, vScaling;
      if ( var-> scaling <= istBox ) {
      	 hScaling = var->scaling & istBoxX;
      	 vScaling = var->scaling & istBoxY;
      } else {
      	 hScaling = vScaling = 1;
      }
      ic_stretch( var->maskType, var-> mask, oldW, oldH, newMask, width, height, hScaling, vScaling);
   }      
   inherited stretch( self, width, height);
   free( var-> mask);
   var->mask = newMask;
   var->maskLine = lineSize;
   var->maskSize = lineSize * abs( height);
   inherited stretch( self, width, height);
   var-> autoMasking = am;
}

void
Icon_create_empty( Handle self, int width, int height, int type)
{
   my-> create_empty_icon( self, width, height, type, imbpp1);
}

void
Icon_create_empty_icon( Handle self, int width, int height, int type, int maskType)
{
   inherited create_empty( self, width, height, type);
   free( var-> mask);
   if ( var-> data)
   {
      var-> maskType = maskType;
      var-> maskLine = LINE_SIZE( var-> w, var-> maskType );
      var-> maskSize = var-> maskLine * var-> h;
      if ( !( var-> mask = allocb( var-> maskSize)) && var-> maskSize > 0) {
         my-> make_empty( self);
         warn("Not enough memory: %d bytes", var-> maskSize);
         return;
      }
      memset( var-> mask, 0, var-> maskSize);
   }
   else {
      var-> mask = nil;
      var-> maskLine = 0;
      var-> maskSize = 0;
   }
}

Handle
Icon_dup( Handle self)
{
   Handle h = inherited dup( self);
   PIcon  i = ( PIcon) h;

   if ( var-> maskType != imbpp1 ) {
      Byte * p;
      if ( !(p = realloc( i-> mask, var-> maskSize ))) {
          warn("Icon::dup: cannot allocate %d bytes", var->maskSize);
	  Object_destroy(h);
	  return nilHandle;
      }
      i-> mask = p;
   }

   i-> autoMasking = var-> autoMasking;
   i-> maskType    = var-> maskType;
   i-> maskColor   = var-> maskColor;
   i-> maskIndex   = var-> maskIndex;
   i-> maskSize    = var-> maskSize;

   memcpy( i-> mask, var-> mask, var-> maskSize);
   return h;
}

IconHandle
Icon_split( Handle self)
{
   IconHandle ret = {0,0};
   PImage i;
   HV * profile = newHV();
   char* className = var-> self-> className;

   pset_H( owner,        var-> owner);
   pset_i( width,        var-> w);
   pset_i( height,       var-> h);
   pset_i( type,         var->maskType|imGrayScale);
   pset_i( conversion,   var->conversion);
   pset_i( scaling,      var->scaling);
   pset_i( preserveType, is_opt( optPreserveType));

   ret. andMask = Object_create( "Prima::Image", profile);
   sv_free(( SV *) profile);
   i = ( PImage) ret. andMask;
   memcpy( i-> data, var-> mask, var-> maskSize);
   i-> self-> update_change(( Handle) i);

   var-> self-> className = inherited className;
   ret. xorMask         = inherited dup( self);
   var-> self-> className = className;

   --SvREFCNT( SvRV( i-> mate));
   return ret;
}

void
Icon_combine( Handle self, Handle xorMask, Handle andMask)
{
   Bool killAM = 0;
   int maskType;
   
   if ( !kind_of( xorMask, CImage) || !kind_of( andMask, CImage))
      return;

   var-> autoMasking = amNone;
   maskType = PImage( andMask)-> type & imBPP;
   if ( maskType != imbpp1 && maskType != imbpp8) {
      killAM = 1;
      andMask = CImage( andMask)-> dup( andMask);
      CImage( andMask)-> set_type( andMask, imbpp1);
      maskType = imbpp1;
   }

   my-> create_empty_icon( self, PImage( xorMask)-> w, PImage( xorMask)-> h, PImage( xorMask)-> type, maskType);

   if ( var-> w != PImage( andMask)-> w || var-> h != PImage( andMask)-> h) {
      if ( !killAM) {
         killAM = 1;
         andMask = CImage( andMask)-> dup( andMask);
      }
      CImage( andMask)-> set_size( andMask, my-> get_size( self));
   }

   memcpy( var-> data, PImage( xorMask)-> data, var-> dataSize);
   memcpy( var-> mask, PImage( andMask)-> data, var-> maskSize);
   memcpy( var-> palette, PImage( xorMask)-> palette, 768);
   var-> palSize = PImage( xorMask)-> palSize;

   if ( killAM) Object_destroy( andMask);

   my-> update_change( self);
}

void
Icon_set( Handle self, HV * profile)
{
   dPROFILE;

   if (pexist( maskType)) {
      int maskType = pget_i(maskType);
      if ( maskType == var-> maskType ) pdelete( maskType );
   }

   if ( pexist( maskType) && pexist( mask ))
   {
      free( var-> mask );
      var-> mask = nil;
      my-> set_maskType( self, pget_i( maskType));
      my-> set_mask( self, pget_sv( mask));
      pdelete( maskType);
      pdelete( mask);
   }

   inherited set ( self, profile);
}

Handle
Icon_extract( Handle self, int x, int y, int width, int height)
{
   int nodata = 0;
   Handle h = inherited extract( self, x, y, width, height);
   PIcon i = (PIcon) h;
   unsigned char * mask = var->mask;
   int ls = var->maskLine;

   if ( var->w == 0 || var->h == 0) return h;
   if ( x < 0) x = 0;
   if ( y < 0) y = 0;
   if ( x >= var->w) x = var->w - 1;
   if ( y >= var->h) y = var->h - 1;
   if ( width  + x > var->w) width  = var->w - x;
   if ( height + y > var->h) height = var->h - y;
   if ( width <= 0 ) {
      width = 1;
      nodata = 1;
   }
   if ( height <= 0 ) {
      height = 1;
      nodata = 1;
   }
   if ( nodata ) return h;

   CIcon(h)->set_autoMasking(h, amNone);
   CIcon(h)->set_maskType(h, var-> maskType);
   CIcon(h)->set_maskColor(h, var-> maskColor);

   if ( var->maskType == imbpp8)
      while ( height-- > 0) {
         memcpy( i-> mask + height * i-> maskLine, mask + ( y + height) * ls + x, width);
   } else {
      while ( height-- > 0)
         bc_mono_copy( mask + ( y + height) * ls, i-> mask + height * i-> maskLine, x, width);
   }
   return h;
}

Handle
Icon_bitmap( Handle self)
{
   Handle h;
   Point s;
   HV * profile;

   if ( !apc_sys_get_value(svLayeredWidgets))
      return inherited bitmap(self);
   
   profile = newHV();
   pset_H( owner,        var->owner);
   pset_i( width,        var->w);
   pset_i( height,       var->h);
   pset_sv_noinc( palette,     my->get_palette( self));
   pset_i( type,         dbtLayered);
   h = Object_create( "Prima::DeviceBitmap", profile);
   sv_free(( SV *) profile);
   s = CDrawable( h)-> get_size( h);
   CDrawable( h)-> put_image_indirect( h, self, 0, 0, 0, 0, s.x, s.y, s.x, s.y, ropSrcCopy);
   --SvREFCNT( SvRV( PDrawable( h)-> mate));

   return h;
}

void
Icon_premultiply_alpha( Handle self, SV * alpha)
{
    if ( !alpha || ( SvTYPE( alpha ) == SVt_NULL)) {
       int type = var-> maskType;
       Image dummy;
       /* multiply with self */
       if ( var-> maskType != imbpp8 )
          my-> set_maskType( self, imbpp8 );
       
       img_fill_dummy( &dummy, var-> w, var-> h, imByte, var-> mask, std256gray_palette);
       img_premultiply_alpha_map( self, (Handle) &dummy);

       if ( is_opt( optPreserveType) && var-> maskType != imbpp8 )
          my-> set_maskType( self, type );
    } else
       inherited premultiply_alpha( self, alpha );
}

Bool
Icon_alpha( Handle self, int alpha, int x1, int y1, int x2, int y2)
{
   Image dummy;
   Byte alpha_a8;
   if (opt_InPaint)
      return apc_gp_alpha( self, alpha, x1, y1, x2, y2);
  
   alpha_a8 = alpha;
   img_fill_dummy( &dummy, var-> w, var-> h, var-> maskType | imGrayScale, var-> mask, std256gray_palette);
   img_bar((Handle) &dummy, x1, y1, x2 - x1 + 1, y2 - y1 + 1, ropCopyPut, (void*)&alpha_a8);

   return true;
}

#ifdef __cplusplus
}
#endif
